﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Xml;
using GMap.NET.Internals;
using GMap.NET.Projections;

namespace GMap.NET.MapProviders
{
    public abstract class CloudMadeMapProviderBase : GMapProvider, RoutingProvider, DirectionsProvider
    {
        public readonly string ServerLetters = "abc";
        public readonly string DoubleResolutionString = "@2x";

        public bool DoubleResolution = true;
        public string Key;
        public int StyleID;

        public string Version = "0.3";

        public CloudMadeMapProviderBase()
        {
            MaxZoom = null;
        }

        #region GMapProvider Members

        public override Guid Id
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override string Name
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override PureProjection Projection
        {
            get
            {
                return MercatorProjection.Instance;
            }
        }

        GMapProvider[] _overlays;

        public override GMapProvider[] Overlays
        {
            get
            {
                if (_overlays == null)
                {
                    _overlays = new GMapProvider[] {this};
                }

                return _overlays;
            }
        }

        public override PureImage GetTileImage(GPoint pos, int zoom)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region RoutingProvider Members

        public MapRoute GetRoute(PointLatLng start, PointLatLng end, bool avoidHighways, bool walkingMode, int zoom)
        {
            List<PointLatLng> points = GetRoutePoints(MakeRoutingUrl(start,
                end,
                walkingMode ? TravelTypeFoot : TravelTypeMotorCar,
                LanguageStr,
                "km"));
            MapRoute route = points != null ? new MapRoute(points, walkingMode ? WalkingStr : DrivingStr) : null;
            return route;
        }

        /// <summary>
        ///     NotImplemented
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="avoidHighways"></param>
        /// <param name="walkingMode"></param>
        /// <param name="zoom"></param>
        /// <returns></returns>
        public MapRoute GetRoute(string start, string end, bool avoidHighways, bool walkingMode, int zoom)
        {
            throw new NotImplementedException();
        }

        #region -- internals --

        string MakeRoutingUrl(PointLatLng start, PointLatLng end, string travelType, string language, string units)
        {
            // http://developers.cloudmade.com/projects/routing-http-api/examples/
            // http://routes.cloudmade.com/YOUR-API-KEY-GOES-HERE/api/0.3/start_point,[[transit_point1,...,transit_pointN]],end_point/route_type[/route_type_modifier].output_format[?lang=(en|de)][&units=(km|miles)]
            return string.Format(CultureInfo.InvariantCulture,
                UrlFormat,
                Key,
                Version,
                start.Lat,
                start.Lng,
                end.Lat,
                end.Lng,
                travelType,
                language,
                units);
        }

        List<PointLatLng> GetRoutePoints(string url)
        {
            List<PointLatLng> points = null;
            try
            {
                string route = GMaps.Instance.UseRouteCache
                    ? Cache.Instance.GetContent(url, CacheType.RouteCache, TimeSpan.FromHours(TTLCache))
                    : string.Empty;
                if (string.IsNullOrEmpty(route))
                {
                    route = GetContentUsingHttp(url);
                    if (!string.IsNullOrEmpty(route))
                    {
                        if (GMaps.Instance.UseRouteCache)
                        {
                            Cache.Instance.SaveContent(url, CacheType.RouteCache, route);
                        }
                    }
                }

                #region -- gpx response --

                //<?xml version="1.0" encoding="UTF-8"?>
                //<gpx creator="" version="1.1" xmlns="http://www.topografix.com/GPX/1/1"
                //    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                //    xsi:schemaLocation="http://www.topografix.com/GPX/1/1 gpx.xsd ">
                //    <extensions>
                //        <distance>293</distance>
                //        <time>34</time>
                //        <start>Perckhoevelaan</start>
                //        <end>Goudenregenlaan</end>
                //    </extensions>
                //    <wpt lat="51.17702" lon="4.39630" />
                //    <wpt lat="51.17656" lon="4.39655" />
                //    <wpt lat="51.17639" lon="4.39670" />
                //    <wpt lat="51.17612" lon="4.39696" />
                //    <wpt lat="51.17640" lon="4.39767" />
                //    <wpt lat="51.17668" lon="4.39828" />
                //    <wpt lat="51.17628" lon="4.39874" />
                //    <wpt lat="51.17618" lon="4.39888" />
                //    <rte>
                //        <rtept lat="51.17702" lon="4.39630">
                //            <desc>Head south on Perckhoevelaan, 0.1 km</desc>
                //            <extensions>
                //                <distance>111</distance>
                //                <time>13</time>
                //                <offset>0</offset>
                //                <distance-text>0.1 km</distance-text>
                //                <direction>S</direction>
                //                <azimuth>160.6</azimuth>
                //            </extensions>
                //        </rtept>
                //        <rtept lat="51.17612" lon="4.39696">
                //            <desc>Turn left at Laarstraat, 0.1 km</desc>
                //            <extensions>
                //                <distance>112</distance>
                //                <time>13</time>
                //                <offset>3</offset>
                //                <distance-text>0.1 km</distance-text>
                //                <direction>NE</direction>
                //                <azimuth>58.1</azimuth>
                //                <turn>TL</turn>
                //                <turn-angle>269.0</turn-angle>
                //            </extensions>
                //        </rtept>
                //        <rtept lat="51.17668" lon="4.39828">
                //            <desc>Turn right at Goudenregenlaan, 70 m</desc>
                //            <extensions>
                //                <distance>70</distance>
                //                <time>8</time>
                //                <offset>5</offset>
                //                <distance-text>70 m</distance-text>
                //                <direction>SE</direction>
                //                <azimuth>143.4</azimuth>
                //                <turn>TR</turn>
                //                <turn-angle>89.8</turn-angle>
                //            </extensions>
                //        </rtept>
                //    </rte>
                //</gpx> 

                #endregion

                if (!string.IsNullOrEmpty(route))
                {
                    XmlDocument xmldoc = new XmlDocument();
                    xmldoc.LoadXml(route);
                    XmlNamespaceManager xmlnsManager = new XmlNamespaceManager(xmldoc.NameTable);
                    xmlnsManager.AddNamespace("sm", "http://www.topografix.com/GPX/1/1");

                    XmlNodeList wpts = xmldoc.SelectNodes("/sm:gpx/sm:wpt", xmlnsManager);
                    if (wpts != null && wpts.Count > 0)
                    {
                        points = new List<PointLatLng>();
                        foreach (XmlNode w in wpts)
                        {
                            double lat = double.Parse(w.Attributes["lat"].InnerText, CultureInfo.InvariantCulture);
                            double lng = double.Parse(w.Attributes["lon"].InnerText, CultureInfo.InvariantCulture);
                            points.Add(new PointLatLng(lat, lng));
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("GetRoutePoints: " + ex);
            }

            return points;
        }

        static readonly string UrlFormat =
            "http://routes.cloudmade.com/{0}/api/{1}/{2},{3},{4},{5}/{6}.gpx?lang={7}&units={8}";

        static readonly string TravelTypeFoot = "foot";
        static readonly string TravelTypeMotorCar = "car";
        static readonly string WalkingStr = "Walking";
        static readonly string DrivingStr = "Driving";

        #endregion

        #endregion

        #region DirectionsProvider Members

        public DirectionsStatusCode GetDirections(out GDirections direction, PointLatLng start, PointLatLng end,
            bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric)
        {
            return GetDirectionsUrl(MakeRoutingUrl(start,
                    end,
                    walkingMode ? TravelTypeFoot : TravelTypeMotorCar,
                    LanguageStr,
                    metric ? "km" : "miles"),
                out direction);
        }

        /// <summary>
        ///     NotImplemented
        /// </summary>
        /// <param name="direction"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="avoidHighways"></param>
        /// <param name="avoidTolls"></param>
        /// <param name="walkingMode"></param>
        /// <param name="sensor"></param>
        /// <param name="metric"></param>
        /// <returns></returns>
        public DirectionsStatusCode GetDirections(out GDirections direction, string start, string end,
            bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///     NotImplemented
        /// </summary>
        /// <param name="status"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="avoidHighways"></param>
        /// <param name="avoidTolls"></param>
        /// <param name="walkingMode"></param>
        /// <param name="sensor"></param>
        /// <param name="metric"></param>
        /// <returns></returns>
        public IEnumerable<GDirections> GetDirections(out DirectionsStatusCode status, string start, string end,
            bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///     NotImplemented
        /// </summary>
        /// <param name="status"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="avoidHighways"></param>
        /// <param name="avoidTolls"></param>
        /// <param name="walkingMode"></param>
        /// <param name="sensor"></param>
        /// <param name="metric"></param>
        /// <returns></returns>
        public IEnumerable<GDirections> GetDirections(out DirectionsStatusCode status, PointLatLng start,
            PointLatLng end, bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///     NotImplemented
        /// </summary>
        /// <param name="direction"></param>
        /// <param name="start"></param>
        /// <param name="wayPoints"></param>
        /// <param name="end"></param>
        /// <param name="avoidHighways"></param>
        /// <param name="avoidTolls"></param>
        /// <param name="walkingMode"></param>
        /// <param name="sensor"></param>
        /// <param name="metric"></param>
        /// <returns></returns>
        public DirectionsStatusCode GetDirections(out GDirections direction, PointLatLng start,
            IEnumerable<PointLatLng> wayPoints, PointLatLng end, bool avoidHighways, bool avoidTolls, bool walkingMode,
            bool sensor, bool metric)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///     NotImplemented
        /// </summary>
        /// <param name="direction"></param>
        /// <param name="start"></param>
        /// <param name="wayPoints"></param>
        /// <param name="end"></param>
        /// <param name="avoidHighways"></param>
        /// <param name="avoidTolls"></param>
        /// <param name="walkingMode"></param>
        /// <param name="sensor"></param>
        /// <param name="metric"></param>
        /// <returns></returns>
        public DirectionsStatusCode GetDirections(out GDirections direction, string start,
            IEnumerable<string> wayPoints, string end, bool avoidHighways, bool avoidTolls, bool walkingMode,
            bool sensor, bool metric)
        {
            throw new NotImplementedException();
        }

        #region -- internals --

        DirectionsStatusCode GetDirectionsUrl(string url, out GDirections direction)
        {
            DirectionsStatusCode ret = DirectionsStatusCode.UNKNOWN_ERROR;
            direction = null;

            try
            {
                string route = GMaps.Instance.UseRouteCache
                    ? Cache.Instance.GetContent(url, CacheType.DirectionsCache, TimeSpan.FromHours(TTLCache))
                    : string.Empty;
                if (string.IsNullOrEmpty(route))
                {
                    route = GetContentUsingHttp(url);
                    if (!string.IsNullOrEmpty(route))
                    {
                        if (GMaps.Instance.UseRouteCache)
                        {
                            Cache.Instance.SaveContent(url, CacheType.DirectionsCache, route);
                        }
                    }
                }

                #region -- gpx response --

                //<?xml version="1.0" encoding="UTF-8"?>
                //<gpx creator="" version="1.1" xmlns="http://www.topografix.com/GPX/1/1"
                //    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                //    xsi:schemaLocation="http://www.topografix.com/GPX/1/1 gpx.xsd ">
                //    <extensions>
                //        <distance>293</distance>
                //        <time>34</time>
                //        <start>Perckhoevelaan</start>
                //        <end>Goudenregenlaan</end>
                //    </extensions>
                //    <wpt lat="51.17702" lon="4.39630" />
                //    <wpt lat="51.17656" lon="4.39655" />
                //    <wpt lat="51.17639" lon="4.39670" />
                //    <wpt lat="51.17612" lon="4.39696" />
                //    <wpt lat="51.17640" lon="4.39767" />
                //    <wpt lat="51.17668" lon="4.39828" />
                //    <wpt lat="51.17628" lon="4.39874" />
                //    <wpt lat="51.17618" lon="4.39888" />
                //    <rte>
                //        <rtept lat="51.17702" lon="4.39630">
                //            <desc>Head south on Perckhoevelaan, 0.1 km</desc>
                //            <extensions>
                //                <distance>111</distance>
                //                <time>13</time>
                //                <offset>0</offset>
                //                <distance-text>0.1 km</distance-text>
                //                <direction>S</direction>
                //                <azimuth>160.6</azimuth>
                //            </extensions>
                //        </rtept>
                //        <rtept lat="51.17612" lon="4.39696">
                //            <desc>Turn left at Laarstraat, 0.1 km</desc>
                //            <extensions>
                //                <distance>112</distance>
                //                <time>13</time>
                //                <offset>3</offset>
                //                <distance-text>0.1 km</distance-text>
                //                <direction>NE</direction>
                //                <azimuth>58.1</azimuth>
                //                <turn>TL</turn>
                //                <turn-angle>269.0</turn-angle>
                //            </extensions>
                //        </rtept>
                //        <rtept lat="51.17668" lon="4.39828">
                //            <desc>Turn right at Goudenregenlaan, 70 m</desc>
                //            <extensions>
                //                <distance>70</distance>
                //                <time>8</time>
                //                <offset>5</offset>
                //                <distance-text>70 m</distance-text>
                //                <direction>SE</direction>
                //                <azimuth>143.4</azimuth>
                //                <turn>TR</turn>
                //                <turn-angle>89.8</turn-angle>
                //            </extensions>
                //        </rtept>
                //    </rte>
                //</gpx> 

                #endregion

                if (!string.IsNullOrEmpty(route))
                {
                    XmlDocument xmldoc = new XmlDocument();
                    xmldoc.LoadXml(route);
                    XmlNamespaceManager xmlnsManager = new XmlNamespaceManager(xmldoc.NameTable);
                    xmlnsManager.AddNamespace("sm", "http://www.topografix.com/GPX/1/1");

                    XmlNodeList wpts = xmldoc.SelectNodes("/sm:gpx/sm:wpt", xmlnsManager);
                    if (wpts != null && wpts.Count > 0)
                    {
                        ret = DirectionsStatusCode.OK;

                        direction = new GDirections();
                        direction.Route = new List<PointLatLng>();

                        foreach (XmlNode w in wpts)
                        {
                            double lat = double.Parse(w.Attributes["lat"].InnerText, CultureInfo.InvariantCulture);
                            double lng = double.Parse(w.Attributes["lon"].InnerText, CultureInfo.InvariantCulture);
                            direction.Route.Add(new PointLatLng(lat, lng));
                        }

                        if (direction.Route.Count > 0)
                        {
                            direction.StartLocation = direction.Route[0];
                            direction.EndLocation = direction.Route[direction.Route.Count - 1];
                        }

                        XmlNode n = xmldoc.SelectSingleNode("/sm:gpx/sm:metadata/sm:copyright/sm:license",
                            xmlnsManager);
                        if (n != null)
                        {
                            direction.Copyrights = n.InnerText;
                        }

                        n = xmldoc.SelectSingleNode("/sm:gpx/sm:extensions/sm:distance", xmlnsManager);
                        if (n != null)
                        {
                            direction.Distance = n.InnerText + "m";
                        }

                        n = xmldoc.SelectSingleNode("/sm:gpx/sm:extensions/sm:time", xmlnsManager);
                        if (n != null)
                        {
                            direction.Duration = n.InnerText + "s";
                        }

                        n = xmldoc.SelectSingleNode("/sm:gpx/sm:extensions/sm:start", xmlnsManager);
                        if (n != null)
                        {
                            direction.StartAddress = n.InnerText;
                        }

                        n = xmldoc.SelectSingleNode("/sm:gpx/sm:extensions/sm:end", xmlnsManager);
                        if (n != null)
                        {
                            direction.EndAddress = n.InnerText;
                        }

                        wpts = xmldoc.SelectNodes("/sm:gpx/sm:rte/sm:rtept", xmlnsManager);
                        if (wpts != null && wpts.Count > 0)
                        {
                            direction.Steps = new List<GDirectionStep>();

                            foreach (XmlNode w in wpts)
                            {
                                GDirectionStep step = new GDirectionStep();

                                double lat = double.Parse(w.Attributes["lat"].InnerText, CultureInfo.InvariantCulture);
                                double lng = double.Parse(w.Attributes["lon"].InnerText, CultureInfo.InvariantCulture);

                                step.StartLocation = new PointLatLng(lat, lng);

                                XmlNode nn = w.SelectSingleNode("sm:desc", xmlnsManager);
                                if (nn != null)
                                {
                                    step.HtmlInstructions = nn.InnerText;
                                }

                                nn = w.SelectSingleNode("sm:extensions/sm:distance-text", xmlnsManager);
                                if (nn != null)
                                {
                                    step.Distance = nn.InnerText;
                                }

                                nn = w.SelectSingleNode("sm:extensions/sm:time", xmlnsManager);
                                if (nn != null)
                                {
                                    step.Duration = nn.InnerText + "s";
                                }

                                direction.Steps.Add(step);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ret = DirectionsStatusCode.EXCEPTION_IN_CODE;
                direction = null;
                Debug.WriteLine("GetDirectionsUrl: " + ex);
            }

            return ret;
        }

        #endregion

        #endregion
    }

    /// <summary>
    ///     CloudMadeMap demo provider, http://maps.cloudmade.com/
    /// </summary>
    public class CloudMadeMapProvider : CloudMadeMapProviderBase
    {
        public static readonly CloudMadeMapProvider Instance;

        CloudMadeMapProvider()
        {
            Key = "5937c2bd907f4f4a92d8980a7c666ac0"; // demo key of CloudMade
            StyleID = 45363; // grab your style here http://maps.cloudmade.com/?styleId=45363
        }

        static CloudMadeMapProvider()
        {
            Instance = new CloudMadeMapProvider();
        }

        #region GMapProvider Members

        public override Guid Id
        {
            get;
        } = new Guid("00403A36-725F-4BC4-934F-BFC1C164D003");

        public override string Name
        {
            get;
        } = "CloudMade, Demo";

        public override PureImage GetTileImage(GPoint pos, int zoom)
        {
            string url = MakeTileImageUrl(pos, zoom, LanguageStr);

            return GetTileImageUsingHttp(url);
        }

        #endregion

        string MakeTileImageUrl(GPoint pos, int zoom, string language)
        {
            return string.Format(UrlFormat,
                ServerLetters[GetServerNum(pos, 3)],
                Key,
                StyleID,
                DoubleResolution ? DoubleResolutionString : string.Empty,
                zoom,
                pos.X,
                pos.Y);
        }

        static readonly string UrlFormat = "http://{0}.tile.cloudmade.com/{1}/{2}{3}/256/{4}/{5}/{6}.png";
    }
}
